package com.pai4j.user.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.pai4j.common.bean.PAIPageResponseBeanUtil;
import com.pai4j.common.enums.CommonStatusEnum;
import com.pai4j.common.util.IDGenerateUtil;
import com.pai4j.common.util.JsonUtil;
import com.pai4j.common.util.ListUtil;
import com.pai4j.common.util.RedisUtil;
import com.pai4j.domain.vo.request.request.SlideVerificationVO;
import com.pai4j.user.repository.entity.SlideVerificationEntity;
import com.pai4j.user.repository.dao.ISlideVerificationEntityDAO;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import com.pai4j.common.enums.RedisKeyEnum;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;

/**
 *  滑块验证服务类
 *
 * @author: CYM-pai
 * @date: 2025/06/29 21:20
 **/
@Service
public class SlideVerificationService {


    @Autowired
    private ISlideVerificationEntityDAO slideVerificationEntityDAO;

    @Autowired
    private RedisUtil redisUtil;

    private static final int OFFSET = 5;

    private static final int[] X_ARR = new int[] {100, 110, 120, 130, 140, 150, 160, 170, 180, 190, 200, 210, 220, 230, 240, 250, 260, 270, 280};
    private static final int[] Y_ARR = new int[] {40, 50, 60, 70, 80, 90, 100, 110, 120};


    public Long save(SlideVerificationVO slideVerificationVO) {
        SlideVerificationEntity slideVerificationEntity = convert(slideVerificationVO);
        slideVerificationEntity = slideVerificationEntityDAO.save(slideVerificationEntity);
        return slideVerificationEntity.getId();
    }

    public void updateStatus(Long id, Integer status) {
        SlideVerificationEntity slideVerificationEntity= slideVerificationEntityDAO.findById(id).orElse(null);
        Assert.isTrue(slideVerificationEntity != null , "滑块验证信息编辑失败：未查找到！");
        slideVerificationEntity.setStatus(status);
        slideVerificationEntityDAO.save(slideVerificationEntity);
        /**
         * 刷新缓存
         */
        this.refreshSlideVerificationCache();
    }

    /**
     * 获取滑块验证信息
     * 
     * @return 返回包含滑块验证数据的VO对象，若缓存不存在且无法刷新则返回null
     * 
     * 1. 检查Redis缓存是否存在，不存在时刷新缓存
     * 2. 解析缓存数据并随机打乱列表顺序
     * 3. 随机生成滑块坐标位置
     * 4. 缓存生成的验证token
     */
    public SlideVerificationVO get() {
        String key = RedisKeyEnum.SLIDE_VERIFICATION_CACHE.getKey();
        if (!redisUtil.exists(key)) {
            this.refreshSlideVerificationCache();
        }
        String cache = redisUtil.get(key);
        if (StringUtils.isBlank(cache)) {
            return null;
        }
        // 解析缓存数据并随机打乱顺序
        List<String> cacheList = JsonUtil.fromJson(cache, new TypeReference<List<String>>() { });
        ListUtil.shuffle(cacheList);
        String image = cacheList.get(0);
        SlideVerificationVO slideVerificationVO = new SlideVerificationVO();
        slideVerificationVO.setImage(image);
        slideVerificationVO.setId(IDGenerateUtil.createId(""));
        // 随机生成X/Y坐标位置
        slideVerificationVO.setX(X_ARR[new Random().nextInt(X_ARR.length)]);
        slideVerificationVO.setY(Y_ARR[new Random().nextInt(Y_ARR.length)]);

        /**
         * 缓存token
         */
        this.cacheToken(slideVerificationVO);

        return slideVerificationVO;
    }

    
    /**
     * 验证token结果
     *
     * @param accessToken 访问令牌，包含用户ID和滑块位置信息
     */
    public boolean validate(String accessToken) {
        String id = accessToken.substring(0, 32);
        int x = Integer.parseInt(accessToken.substring(32, accessToken.length()));
        SlideVerificationVO slideVerification = getToken(id);
        Assert.isTrue(slideVerification != null, "滑块验证失败");
        Assert.isTrue(Math.abs(x - slideVerification.getX()) < OFFSET, "滑块验证失败");
        // 验证成功
        cacheTokenValidateResult(id);
        return true;
    }

    /**
     * 验证滑块验证令牌的结果
     *
     * @param id 用户ID或会话ID，用于获取对应的Redis键
     *
     * 此方法用于验证滑块验证令牌的结果是否有效它通过从Redis缓存中获取验证结果并进行检查
     * 如果验证结果不是"1"，则抛出异常，表示滑块验证失败
     */
    public void validateTokenResult(String id) {
        // 根据用户ID或会话ID生成Redis键，用于存储或获取滑块验证令牌的验证结果
        String tokenKey = RedisKeyEnum.SLIDE_VERIFICATION_TOKEN_VALIDATE_RESULT_CACHE.getKey(id);
        
        // 从Redis缓存中获取滑块验证令牌的验证结果
        String cache = redisUtil.get(tokenKey);
        
        // 断言验证结果等于"1"，否则抛出异常，表示滑块验证失败
        Assert.isTrue(ObjectUtils.equals(cache, "1"), "滑块验证失败");
    }

    private void cacheTokenValidateResult(String id) {
        String tokenKey = RedisKeyEnum.SLIDE_VERIFICATION_TOKEN_VALIDATE_RESULT_CACHE.getKey(id);
        redisUtil.set(tokenKey, "1", RedisKeyEnum.SLIDE_VERIFICATION_TOKEN_VALIDATE_RESULT_CACHE.getExpireTime());
    }

    public SlideVerificationVO getToken(String tokenId) {
        String tokenKey = RedisKeyEnum.SLIDE_VERIFICATION_TOKEN_CACHE.getKey(tokenId);
        String cache = redisUtil.get(tokenKey);
        Assert.isTrue(StringUtils.isNotBlank(cache), "滑动验证失效，请刷新重新验证！");
        return JsonUtil.fromJson(cache, SlideVerificationVO.class);
    }

    private void cacheToken(SlideVerificationVO slideVerificationVO) {
        String tokenId = slideVerificationVO.getId();
        String tokenKey = RedisKeyEnum.SLIDE_VERIFICATION_TOKEN_CACHE.getKey(tokenId);
        redisUtil.set(tokenKey, JsonUtil.toJsonString(slideVerificationVO), RedisKeyEnum.SLIDE_VERIFICATION_TOKEN_CACHE.getExpireTime());
    }


    public PAIPageResponseBeanUtil<SlideVerificationVO> pageList(Integer status, int pageNo, int pageSize) {
        Pageable pageable = PageRequest.of(pageNo - 1, pageSize);
        Page<SlideVerificationEntity> page;
        if (status != null) {
            page = slideVerificationEntityDAO.findAllByStatus(status, pageable);
        } else {
            page = slideVerificationEntityDAO.findAll(pageable);
        }
        List<SlideVerificationEntity> slideVerificationEntities = new ArrayList<>();
        if (!CollectionUtils.isEmpty(page.getContent())) {
            slideVerificationEntities = page.getContent();
        }
        return PAIPageResponseBeanUtil.success(pageNo, pageSize, page.getTotalElements(), slideVerificationEntities);
    }

    private void refreshSlideVerificationCache() {
        List<SlideVerificationEntity> list = slideVerificationEntityDAO.findAllByStatus(CommonStatusEnum.PUBLISHED.getStatusCode());
        if (CollectionUtils.isEmpty(list)) {
            return;
        }
        List<String> voList = list.stream().map(SlideVerificationEntity::getImage).collect(Collectors.toList());
        String key = RedisKeyEnum.SLIDE_VERIFICATION_CACHE.getKey();
        redisUtil.remove(key);
        redisUtil.set(key, JsonUtil.toJsonString(voList), RedisKeyEnum.SLIDE_VERIFICATION_CACHE.getExpireTime());
    }

    private SlideVerificationEntity convert(SlideVerificationVO slideVerificationVO) {
        SlideVerificationEntity slideVerificationEntity = new SlideVerificationEntity();
        slideVerificationEntity.setStatus(CommonStatusEnum.NEW.getStatusCode());
        slideVerificationEntity.setImage(slideVerificationVO.getImage());
        return slideVerificationEntity;
    }

}
